﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using Accord.Statistics.Models.Markov;
using Biosek;

namespace DestinoSegmentador.Models
{
	/// <summary>
	/// Loader for Hidden Markov Models exported using FastHMM (https://code.google.com/p/fast-hmm/)
	/// </summary>
	[Export(typeof(IPatternMatchingModelLoader))]
	public class HiddenMarkovModel_FastHMM_Loader : IPatternMatchingModelLoader
	{
		#region Miembros de IPatternMatchingModelLoader

		public enum ParsingState { header, probabilities, transitions, emissions, success };

		double doubleParse(string str)
		{
			if (str == "inf" || str == "+inf") return double.PositiveInfinity;
			else if (str == "-inf") return double.NegativeInfinity;
			else return double.Parse(str, CultureInfo.InvariantCulture);
		}

		/// <summary>
		/// Importer code reference
		/// https://fast-hmm.googlecode.com/svn/trunk/FastHMM/HiddenMarkovModelExporter.cpp
		/// </summary>
		/// <param name="stream"></param>
		/// <returns></returns>
		public IPatternMatchingModel LoadModel(Stream stream)
		{
			int model_states = 0;
			int model_symbols = 0;
			double[] model_probabilities = null;
			double[,] model_transitions = null;
			double[,] model_emissions = null;

			var file = new StreamReader(stream);
			int lineNumber = 0;
			int counter = 0;
			var state = ParsingState.header;
			while (!file.EndOfStream)
			{
				var line = file.ReadLine().Trim();
				lineNumber++;
				if (line.StartsWith("#")) continue;
				if (state == ParsingState.header)
				{
					if (string.IsNullOrEmpty(line))
					{
						state = ParsingState.probabilities;
						counter = 0;
						continue;
					}
					var splits = line.Split(' ');
					if (splits.Length != 2)
					{
						throw new InvalidOperationException("Invalid model file. Bad formed header");
					}
					model_states = int.Parse(splits[0], CultureInfo.InvariantCulture);
					model_symbols = int.Parse(splits[1], CultureInfo.InvariantCulture);
					model_transitions = new double[model_states, model_states];
					model_emissions = new double[model_states, model_symbols];
				}
				else if (state == ParsingState.probabilities)
				{
					if (string.IsNullOrEmpty(line))
					{
						state = ParsingState.transitions;
						counter = 0;
						continue;
					}
					var splits = line.Split(' ');
					var width = splits.Length;
					if (width != model_states) throw new InvalidOperationException("Invalid model file. Bad formed probabilities vector");
					model_probabilities = new double[width];
					for (int i = 0; i < width; i++)
					{
						var val = doubleParse(splits[i]);
						model_probabilities[i] = val;
					}
				}
				else if (state == ParsingState.transitions)
				{
					if (string.IsNullOrEmpty(line))
					{
						if (counter != model_states) throw new InvalidOperationException("Invalid model file. Bad formed transitions matrix");
						state = ParsingState.emissions;
						counter = 0;
						continue;
					}
					var splits = line.Split(' ');
					var width = splits.Length;
					if (width != model_states) throw new InvalidOperationException("Invalid model file. Bad formed transitions matrix");
					for (int i = 0; i < width; i++)
					{
						var val = doubleParse(splits[i]);
						model_transitions[counter, i] = val;
					}
					counter++;
				}
				else if (state == ParsingState.emissions)
				{
					if (string.IsNullOrEmpty(line))
					{
						if (counter != model_states) throw new InvalidOperationException("Invalid model file. Bad formed emissions matrix");
						state = ParsingState.success;
						counter = 0;
						continue;
					}
					var splits = line.Split(' ');
					var width = splits.Length;
					if (width != model_symbols) throw new InvalidOperationException("Invalid model file. Bad formed emissions matrix");
					for (int i = 0; i < width; i++)
					{
						var val = doubleParse(splits[i]);
						model_emissions[counter, i] = val;
					}
					counter++;
				}
			}
			if (state == ParsingState.emissions && counter == model_states)
			{
				state = ParsingState.success;
			}
			if (state != ParsingState.success)
			{
				throw new InvalidOperationException("Error reading model file");
			}
			var model = new _HiddenMarkovModel(model_transitions, model_emissions, model_probabilities, true);
			return model;
		}

		#endregion
	}

	/// <summary>
	/// Hidden Markov Model wrapper implementing <typeparamref name="IPatterMatchingModel"/>
	/// </summary>
	public class _HiddenMarkovModel : HiddenMarkovModel, IPatternMatchingModel
	{
		/// <summary>
		/// Constructor for Hidden Markov Model
		/// </summary>
		/// <param name="transitions">Transition Matrix</param>
		/// <param name="emissions">Emissions Matrix</param>
		/// <param name="initial">Initial probabilities vector</param>
		/// <param name="logarithm">use logarithmic values</param>
		public _HiddenMarkovModel(double[,] transitions, double[,] emissions, double[] initial, bool logarithm = false)
			: base(transitions, emissions, initial, logarithm)
		{
		}

		#region Miembros de IPatternMatchingModel

		/// <summary>
		/// Accepted sample data types
		/// </summary>
		public IEnumerable<Type> AcceptedSampleTypes
		{
			get { yield return typeof(int[]); }
		}

		/// <summary>
		/// Evaluate matching as real value
		/// </summary>
		/// <param name="samples">Sample set</param>
		/// <returns>Matching value for each sample</returns>
		public IEnumerable<float> EvaluateMatch(IEnumerable samples)
		{
			foreach (int[] sample in samples)
			{
				yield return (float)this.Evaluate(sample);
			}
		}

		/// <summary>
		/// Evaluate matching as binary value.
		/// <remarks>NOT IMPLEMENTED FOR HMM</remarks>
		/// </summary>
		/// <param name="samples">Sample set</param>
		/// <returns>Matching value for each sample</returns>
		public IEnumerable<bool> EvaluateMatchBinary(IEnumerable samples)
		{
			throw new NotImplementedException();
		}

		/// <summary>
		/// Because HMM outputs a probability matching indicator, its output data type is not binary.
		/// </summary>
		public bool IsBinaryResponse
		{
			get { return false; }
		}

		#endregion
	}
}
